package lang

import (
	"fmt"
	"gvm/native/basic"
	"gvm/rtda"
	"gvm/rtda/heap"
)

type StackTraceElement struct {
	fileName   string
	className  string
	methodName string
	lineNumber int
}

func (ste *StackTraceElement) String() string {
	return fmt.Sprintf("%s.%s(%s:%d)",
		ste.className, ste.methodName, ste.fileName, ste.lineNumber)
}

func RegisterThrowable() (className string, methods []*basic.NaMethod) {
	return "java/lang/Throwable", []*basic.NaMethod{
		{
			"fillInStackTrace",
			"(I)Ljava/lang/Throwable;",
			fillInStackTrace,
		},
	}
}

// private native Throwable fillInStackTrace(int dummy);
func fillInStackTrace(jFrame *rtda.JFrame) {
	this := jFrame.LocalVars().GetThis()
	jFrame.OperandStack().PushRef(this)

	stes := createStackTraceElements(this, jFrame.JThread())
	this.SetExtra(stes)
}

func createStackTraceElements(tObj *heap.Object, jThread *rtda.JThread) []*StackTraceElement {
	skip := distanceToObject(tObj.Class()) + 2
	jFrames := jThread.GetJFrames()[skip:]
	stes := make([]*StackTraceElement, len(jFrames))
	for i, jFrame := range jFrames {
		stes[i] = createStackTraceElement(jFrame)
	}
	return stes
}

func createStackTraceElement(jFrame *rtda.JFrame) *StackTraceElement {
	method := jFrame.Method()
	class := method.Class()
	return &StackTraceElement{
		fileName:   class.SourceFile(),
		className:  class.Name(),
		methodName: method.Name(),
		lineNumber: method.GetLineNumber(jFrame.NextPC() - 1),
	}
}

func distanceToObject(class *heap.Klass) int {
	distance := 0
	for c := class.SuperClass(); c != nil; c = c.SuperClass() {
		distance++
	}
	return distance
}
